/*
 * contrib/hstore/hstore_gin.c
 */
#include "postgres.h"

#include "access/gin.h"
#include "access/stratnum.h"
#include "catalog/pg_type.h"

#include "hstore.h"


/*
 * 在使用GIN索引进行hstore时，我们选择同时对键和值进行索引。
 * 存储格式为“text”值，在字符串前添加K、V或N以表示键、值或空值。
 * （从9.1开始，存储空值为null可能更好，但为了与磁盘上的兼容性，我们会保持这种方式。）
 */
#define KEYFLAG		'K'
#define VALFLAG		'V'
#define NULLFLAG	'N'

PG_FUNCTION_INFO_V1(gin_extract_hstore);

/* 构建一个可索引的文本值 */
static text * fc_makeitem(char *fc_str, int fc_len, char fc_flag)
{
	text	   *fc_item;

	fc_item = (text *) palloc(VARHDRSZ + fc_len + 1);
	SET_VARSIZE(fc_item, VARHDRSZ + fc_len + 1);

	*VARDATA(fc_item) = fc_flag;

	if (fc_str && fc_len > 0)
		memcpy(VARDATA(fc_item) + 1, fc_str, fc_len);

	return fc_item;
}

Datum gin_extract_hstore(PG_FUNCTION_ARGS)
{
	HStore	   *fc_hs = PG_GETARG_HSTORE_P(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	Datum	   *fc_entries = NULL;
	HEntry	   *fc_hsent = ARRPTR(fc_hs);
	char	   *fc_ptr = STRPTR(fc_hs);
	int			fc_count = HS_COUNT(fc_hs);
	int			fc_i;

	*fc_nentries = 2 * fc_count;
	if (fc_count)
		fc_entries = (Datum *) palloc(sizeof(Datum) * 2 * fc_count);

	for (fc_i = 0; fc_i < fc_count; ++fc_i)
	{
		text	   *fc_item;

		fc_item = fc_makeitem(HSTORE_KEY(fc_hsent, fc_ptr, fc_i),
						HSTORE_KEYLEN(fc_hsent, fc_i),
						KEYFLAG);
		fc_entries[2 * fc_i] = PointerGetDatum(fc_item);

		if (HSTORE_VALISNULL(fc_hsent, fc_i))
			fc_item = fc_makeitem(NULL, 0, NULLFLAG);
		else
			fc_item = fc_makeitem(HSTORE_VAL(fc_hsent, fc_ptr, fc_i),
							HSTORE_VALLEN(fc_hsent, fc_i),
							VALFLAG);
		fc_entries[2 * fc_i + 1] = PointerGetDatum(fc_item);
	}

	PG_RETURN_POINTER(fc_entries);
}

PG_FUNCTION_INFO_V1(gin_extract_hstore_query);

Datum gin_extract_hstore_query(PG_FUNCTION_ARGS)
{
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);
	int32	   *fc_searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *fc_entries;

	if (fc_strategy == HStoreContainsStrategyNumber)
	{
		/* 查询是一个hstore，因此只需应用gin_extract_hstore... */
		fc_entries = (Datum *)
			DatumGetPointer(DirectFunctionCall2(gin_extract_hstore,
												PG_GETARG_DATUM(0),
												PointerGetDatum(fc_nentries)));
		/* ... 除非“包含{}”需要完全的索引扫描 */
		if (fc_entries == NULL)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else if (fc_strategy == HStoreExistsStrategyNumber)
	{
		text	   *fc_query = PG_GETARG_TEXT_PP(0);
		text	   *fc_item;

		*fc_nentries = 1;
		fc_entries = (Datum *) palloc(sizeof(Datum));
		fc_item = fc_makeitem(VARDATA_ANY(fc_query), VARSIZE_ANY_EXHDR(fc_query), KEYFLAG);
		fc_entries[0] = PointerGetDatum(fc_item);
	}
	else if (fc_strategy == HStoreExistsAnyStrategyNumber ||
			 fc_strategy == HStoreExistsAllStrategyNumber)
	{
		ArrayType  *fc_query = PG_GETARG_ARRAYTYPE_P(0);
		Datum	   *fc_key_datums;
		bool	   *fc_key_nulls;
		int			fc_key_count;
		int			fc_i,
					fc_j;
		text	   *fc_item;

		deconstruct_array(fc_query,
						  TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_key_datums, &fc_key_nulls, &fc_key_count);

		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_key_count);

		for (fc_i = 0, fc_j = 0; fc_i < fc_key_count; ++fc_i)
		{
			/* 数组中的null被忽略，参见hstoreArrayToPairs */
			if (fc_key_nulls[fc_i])
				continue;
			fc_item = fc_makeitem(VARDATA(fc_key_datums[fc_i]), VARSIZE(fc_key_datums[fc_i]) - VARHDRSZ, KEYFLAG);
			fc_entries[fc_j++] = PointerGetDatum(fc_item);
		}

		*fc_nentries = fc_j;
		/* 无键的ExistsAll应该匹配所有内容 */
		if (fc_j == 0 && fc_strategy == HStoreExistsAllStrategyNumber)
			*fc_searchMode = GIN_SEARCH_MODE_ALL;
	}
	else
	{
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
		fc_entries = NULL;			/* 保持编译器安静 */
	}

	PG_RETURN_POINTER(fc_entries);
}

PG_FUNCTION_INFO_V1(gin_consistent_hstore);

Datum gin_consistent_hstore(PG_FUNCTION_ARGS)
{
	bool	   *fc_check = (bool *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* HStore	   *query = PG_GETARG_HSTORE_P(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);

	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);
	bool		fc_res = true;
	int32		fc_i;

	if (fc_strategy == HStoreContainsStrategyNumber)
	{
		/*
		 * 索引没有键和值对应关系的信息，因此我们需要重新检查。
		 * 然而，如果并非所有键都存在，我们可以立即失败。
		 */
		*fc_recheck = true;
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (!fc_check[fc_i])
			{
				fc_res = false;
				break;
			}
		}
	}
	else if (fc_strategy == HStoreExistsStrategyNumber)
	{
		/* 在默认搜索模式下，键的存在是有保证的 */
		*fc_recheck = false;
		fc_res = true;
	}
	else if (fc_strategy == HStoreExistsAnyStrategyNumber)
	{
		/* 在默认搜索模式下，键的存在是有保证的 */
		*fc_recheck = false;
		fc_res = true;
	}
	else if (fc_strategy == HStoreExistsAllStrategyNumber)
	{
		/* 检查所有键是否存在会给出准确的结果 */
		*fc_recheck = false;
		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			if (!fc_check[fc_i])
			{
				fc_res = false;
				break;
			}
		}
	}
	else
		elog(ERROR, "unrecognized strategy number: %d", fc_strategy);

	PG_RETURN_BOOL(fc_res);
}
